﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using Utility.Math;
using Utility.Inspector;

namespace Utility.NodeGraphs
{
	[NodeGraphEditor (typeof (NodeGraph))]
	public class NodeGraphEditor
	{
		#region static
		static Dictionary<Type, Type> editorTypeDict;
		static Dictionary<Type, NodeGraphEditor> editorDict = new Dictionary<Type, NodeGraphEditor> ();

		static void CollectEditorTypes ()
		{
			Type t_nodeGraphEditor = typeof (NodeGraphEditor);
			editorTypeDict = (from assembly in AppDomain.CurrentDomain.GetAssemblies ()
							  from type in assembly.GetTypes ()
							  where type.Equals (t_nodeGraphEditor) || type.IsSubclassOf (t_nodeGraphEditor)
							  where !type.IsAbstract
							  let atts = type.GetCustomAttributes (typeof (NodeGraphEditorAttribute), false)
							  where atts != null && atts.Length > 0
							  from obj in atts
							  let att = (NodeGraphEditorAttribute)obj
							  group type by att.targetType)
							 .ToDictionary (group => group.Key, group => group.First ());
		}

		static NodeGraphEditor CreateNodeGraphEditor (Type graphType)
		{
			if (editorTypeDict == null)
			{
				CollectEditorTypes ();
			}

			Type t_graph = typeof (NodeGraph);
			Type editorType = null;
			while (graphType.Equals (t_graph) || graphType.IsSubclassOf (t_graph))
			{
				if (editorTypeDict.TryGetValue (graphType, out editorType))
				{
					break;
				}
				else
				{
					graphType = graphType.BaseType;
				}
			}

			var editor = Activator.CreateInstance (editorType) as NodeGraphEditor;
			if (editor == null)
			{
				Debug.LogWarning ($"Cannot create editor for {graphType}");
			}
			return editor;
		}

		public static NodeGraphEditor GetEditor (Type type)
		{
			NodeGraphEditor editor = null;
			if (!editorDict.TryGetValue (type, out editor))
			{
				editor = CreateNodeGraphEditor (type);
				editorDict[type] = editor;
			}
			return editor;
		}

		public static NodeGraphEditor GetEditor (NodeGraph graph)
		{
			return GetEditor (graph.GetType ());
		}
		#endregion

		static NodeGraphEditorWindow GetWindow (NodeGraph graph) => NodeGraphEditorWindow.GetWindow (graph);

		public NodeGraphEditor () { }

		Texture _canvasTex;
		protected virtual Texture CanvasTexture
		{
			get
			{
				if (_canvasTex == null)
				{
					_canvasTex = Resources.Load<Texture> ("NodeGraph/Grid");
				}
				return _canvasTex;
			}
		}

		public virtual Rect GetCanvasRect (Rect rect, NodeGraph graph)
		{
			return rect;
		}

		public virtual void DrawCanvas (Rect rect, NodeGraph graph)
		{
			var tex = CanvasTexture;
			float texSize = 100;

			int x_count = Mathf.CeilToInt (rect.width / texSize) + 1;
			int y_count = Mathf.CeilToInt (rect.height / texSize) + 1;

			Vector2 offset = new Vector2 (graph.position.x % texSize, graph.position.y % texSize);

			Rect rect_grid;
			for (float x = -1; x < x_count; x++)
			{
				for (float y = -1; y < y_count; y++)
				{
					Vector2 pos = new Vector2 (x, y) * texSize + offset;
					rect_grid = new Rect (pos, new Vector2 (texSize, texSize));
					GUI.DrawTexture (rect_grid, tex);
				}
			}
		}

		public virtual void DrawOverlayGUI (Rect rect, NodeGraph graph)
		{

		}

		#region context menu
		public void OpenContextMenu (NodeGraph graph)
		{
			BuildContextMenu (graph)?.ShowAsContext ();
		}

		public virtual GenericMenu BuildContextMenu (NodeGraph graph)
		{
			GenericMenu menu = new GenericMenu ();
			AddMenuItem_AddNode<Node> (menu, graph, "Add Node");
			AddMenuItem_ClearAll (menu, graph, "Remove All");
			return menu;
		}

		protected void AddMenuItem_AddNode<TNode> (GenericMenu menu, NodeGraph graph, string text, string nodeName = null)
			where TNode : Node
		{
			menu.AddItem (new GUIContent (text), false, () => AddNodeAtMousePosition<TNode> (graph, nodeName));
		}

		protected void AddMenuItem_AddNode<TNode> (GenericMenu menu, NodeGraph graph, Action<TNode> nodeInitAction, string text, string nodeName = null)
			where TNode : Node
		{
			GenericMenu.MenuFunction action = () =>
			{
				var node = AddNodeAtMousePosition<TNode> (graph, nodeName);
				nodeInitAction (node);
			};
			menu.AddItem (new GUIContent (text), false, action);
		}

		protected void AddMenuItem_AddNode<TNode> (GenericMenu menu, NodeGraph graph, Action<TNode, object> nodeInitAction, object context, string text, string nodeName = null)
			where TNode : Node
		{
			GenericMenu.MenuFunction action = () =>
			{
				var node = AddNodeAtMousePosition<TNode> (graph, nodeName);
				nodeInitAction (node, context);
			};
			menu.AddItem (new GUIContent (text), false, action);
		}

		protected void AddMenuItem_ClearAll (GenericMenu menu, NodeGraph graph, string text = "Remove All")
		{
			menu.AddItem (new GUIContent (text), false, () => graph.Clear ());
		}
		#endregion
		#region utility
		protected TNode AddNodeAtMousePosition<TNode> (NodeGraph graph, string name = null)
			where TNode : Node
		{
			var node = graph.AddNewNode<TNode> ();
			node.name = name;
			node.position = GetWindow (graph).MousePosition_Canvas - graph.position;
			return node;
		}
		#endregion
	}
}